/*
 * Copyright 2005-2007 Noelios Consulting.
 * 
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the "License"). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL HEADER in each file and
 * include the License file at http://www.opensource.org/licenses/cddl1.txt If
 * applicable, add the following below this CDDL HEADER, with the fields
 * enclosed by brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 */

package org.restlet.resource;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.restlet.Application;
import org.restlet.Context;
import org.restlet.data.Dimension;
import org.restlet.data.Language;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.data.Reference;
import org.restlet.data.ReferenceList;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.util.Series;
import org.restlet.util.Template;

/**
 * Intended conceptual target of a hypertext reference. "Any information that
 * can be named can be a resource: a document or image, a temporal service (e.g.
 * "today's weather in Los Angeles"), a collection of other resources, a
 * non-virtual object (e.g. a person), and so on. In other words, any concept
 * that might be the target of an author's hypertext reference must fit within
 * the definition of a resource. The only thing that is required to be static
 * for a resource is the semantics of the mapping, since the semantics is what
 * distinguishes one resource from another." Roy T. Fielding<br>
 * <br>
 * Another definition adapted from the URI standard (RFC 3986): a resource is
 * the conceptual mapping to a representation (also known as entity) or set of
 * representations, not necessarily the representation which corresponds to that
 * mapping at any particular instance in time. Thus, a resource can remain
 * constant even when its content (the representations to which it currently
 * corresponds) changes over time, provided that the conceptual mapping is not
 * changed in the process. In addition, a resource is always identified by a
 * URI.<br>
 * <br>
 * Typically created by Finders, Resource instances are the final handlers of
 * calls received by server connectors. Unlike the other handlers in the
 * processing chain, a Resource is generally not shared between calls and
 * doesn't have to be thread-safe. This is the point where the RESTful view of
 * your Web application can be integrated with your domain objects. Those domain
 * objects can be implemented using any technology, relational databases, object
 * databases, transactional components like EJB, etc. You just have to extend
 * this class to override the REST methods you want to support like post(),
 * put() or delete(). The common GET method is supported by the modifiable
 * "variants" list property and the {@link #getRepresentation(Variant)} method.
 * This allows an easy and cheap declaration of the available variants in the
 * constructor for example, then the on-demand creation of costly
 * representations via the {@link #getRepresentation(Variant)} method.<br>
 * <br>
 * At a lower level, you have a handle*(Request,Response) method for each REST
 * method that is supported by the Resource, where the '*' is replaced by the
 * method name. The Finder handler for example, will be able to dynamically
 * dispatch a call to the appropriate handle*() method. Most common REST methods
 * like GET, POST, PUT and DELETE have default implementations that pre-handle
 * calls to do content negotiation for example, based on the higher-level
 * methods that we discussed previously. For example if you want to support a
 * MOVE method, just add an handleMove(Request,Response) method and it will be
 * detected automatically by a Finder handler.<br>
 * <br>
 * Finally, you need to declare which REST methods are allowed by your Resource
 * by overiding the matching allow*() method. By default, allowGet() returns
 * true, but all other allow*() methods will return false. Therefore, if you
 * want to support the DELETE method, just override allowDelete() and return
 * true. Again, a previous Finder handler will be able to detect this method and
 * know whether or not your Resource should be invoked. It is also used by the
 * handleOptions() method to return the list of allowed methods.
 * 
 * @see <a
 *      href="http://roy.gbiv.com/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_1">Source
 *      dissertation</a>
 * @see <a href="http://www.restlet.org/documentation/1.0/tutorial#part12">Tutorial: Reaching
 *      target Resources</a>
 * @see org.restlet.resource.Representation
 * @see org.restlet.Finder
 * @author Jerome Louvel (contact@noelios.com)
 * @author Thierry Boileau (thboileau@gmail.com)
 */
public class Resource {
	/** The parent context. */
	private Context context;

	/** The logger to use. */
	private Logger logger;

	/** Indicates if the best content is automatically negotiated. */
	private boolean negotiateContent;

	/** The handled request. */
	private Request request;

	/** The returned response. */
	private Response response;

	/** The modifiable list of variants. */
	private List<Variant> variants;

	/**
	 * Default constructor. Note that the init() method must be invoked right
	 * after the creation of the resource.
	 */
	public Resource() {
	}

	/**
	 * Constructor. This constructor will invoke the init() method by default.
	 * 
	 * @param context
	 *            The parent context.
	 * @param request
	 *            The request to handle.
	 * @param response
	 *            The response to return.
	 */
	public Resource(Context context, Request request, Response response) {
		init(context, request, response);
	}

	/**
	 * Indicates if it is allowed to delete the resource. The default value is
	 * false.
	 * 
	 * @return True if the method is allowed.
	 */
	public boolean allowDelete() {
		return false;
	}

	/**
	 * Indicates if it is allowed to get the variants. The default value is
	 * true.
	 * 
	 * @return True if the method is allowed.
	 */
	public boolean allowGet() {
		return true;
	}

	/**
	 * Indicates if it is allowed to post to the resource. The default value is
	 * false.
	 * 
	 * @return True if the method is allowed.
	 */
	public boolean allowPost() {
		return false;
	}

	/**
	 * Indicates if it is allowed to put to the resource. The default value is
	 * false.
	 * 
	 * @return True if the method is allowed.
	 */
	public boolean allowPut() {
		return false;
	}

	/**
	 * Asks the resource to delete itself and all its representations.
	 */
	public void delete() {
		getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
	}

	/**
	 * Generates a reference based on a template URI. Note that you can leverage
	 * all the variables defined in the Template class as they will be resolved
	 * using the resource's request and response properties.
	 * 
	 * @param uriTemplate
	 *            The URI template to use for generation.
	 * @return The generated reference.
	 */
	public Reference generateRef(String uriTemplate) {
		Template tplt = new Template(getLogger(), uriTemplate);
		return new Reference(tplt.format(getRequest(), getResponse()));
	}

	/**
	 * Returns the context.
	 * 
	 * @return The context.
	 */
	public Context getContext() {
		if (this.context == null)
			this.context = new Context(getClass().getCanonicalName());
		return this.context;
	}

	/**
	 * Returns the logger to use.
	 * 
	 * @return The logger to use.
	 */
	public Logger getLogger() {
		if (this.logger == null)
			this.logger = getContext().getLogger();
		return this.logger;
	}

	/**
	 * Returns the preferred representation according to the client preferences
	 * specified in the associated request.
	 * 
	 * @return The preferred representation.
	 */
	public Representation getPreferredRepresentation() {
		return getRepresentation(getPreferredVariant());
	}

	/**
	 * Returns the preferred variant according to the client preferences
	 * specified in the associated request.
	 * 
	 * @return The preferred variant.
	 */
	public Variant getPreferredVariant() {
		Variant result = null;
		List<Variant> variants = getVariants();

		if ((variants != null) && (!variants.isEmpty())) {
			Language language = null;
			// Compute the preferred variant. Get the default language
			// preference from the Application (if any).
			Object app = getContext().getAttributes().get(Application.KEY);

			if (app instanceof Application) {
				language = ((Application) app).getMetadataService()
						.getDefaultLanguage();
			}
			result = getRequest().getClientInfo().getPreferredVariant(variants,
					language);

		}

		return result;
	}

	/**
	 * Returns a full representation for a given variant previously returned via
	 * the getVariants() method. The default implementation directly returns the
	 * variant in case the variants are already full representations. In all
	 * other cases, you will need to override this method in order to provide
	 * your own implementation. <br/><br/>
	 * 
	 * This method is very useful for content negotiation when it is too costly
	 * to initilize all the potential representations. It allows a resource to
	 * simply expose the available variants via the getVariants() method and to
	 * actually server the one selected via this method.
	 * 
	 * @param variant
	 *            The variant whose full representation must be returned.
	 * @return The full representation for the variant.
	 * @see #getVariants()
	 */
	public Representation getRepresentation(Variant variant) {
		Representation result = null;

		if (variant instanceof Representation) {
			result = (Representation) variant;
		}

		return result;
	}

	/**
	 * Returns the request.
	 * 
	 * @return the request.
	 */
	public Request getRequest() {
		return this.request;
	}

	/**
	 * Returns the response.
	 * 
	 * @return the response.
	 */
	public Response getResponse() {
		return this.response;
	}

	/**
	 * Returns the modifiable list of variants. A variant can be a purely
	 * descriptive representation, with no actual content that can be served. It
	 * can also be a full representation in case a resource has only one variant
	 * or if the initialization cost is very low.<br>
	 * <br>
	 * Note that the order in which the variants are inserted in the list
	 * matters. For example, if the client has no preference defined, or if the
	 * acceptable variants have the same quality level for the client, the first
	 * acceptable variant in the list will be returned.<br>
	 * <br>
	 * It is recommended to not override this method and to simply use it at
	 * construction time to initialize the list of available variants.
	 * Overriding it will force you to reconstruct the list for each call which
	 * is expensive.
	 * 
	 * @return The list of variants.
	 * @see #getRepresentation(Variant)
	 */
	public List<Variant> getVariants() {
		if (this.variants == null)
			this.variants = new ArrayList<Variant>();
		return this.variants;
	}

	/**
	 * Handles a DELETE call invoking the 'delete' method of the target resource
	 * (as provided by the 'findTarget' method).
	 */
	public void handleDelete() {
		boolean bContinue = true;
		if (getRequest().getConditions().hasSome()) {
			Variant preferredVariant = null;

			if (isNegotiateContent()) {
				preferredVariant = getPreferredVariant();
			} else {
				List<Variant> variants = getVariants();

				if (variants.size() == 1) {
					preferredVariant = variants.get(0);
				} else {
					getResponse().setStatus(
							Status.CLIENT_ERROR_PRECONDITION_FAILED);
					bContinue = false;
				}
			}
			// The conditions have to be checked even if there is no preferred
			// variant.
			if (bContinue) {
				Status status = getRequest().getConditions().getStatus(
						getRequest().getMethod(), preferredVariant);

				if (status != null) {
					getResponse().setStatus(status);
					bContinue = false;
				}
			}
		}

		if (bContinue) {
			delete();
		}

	}

	/**
	 * Handles a GET call by automatically returning the best entity available
	 * from the target resource (as provided by the 'findTarget' method). The
	 * content negotiation is based on the client's preferences available in the
	 * handled call and can be turned off using the "negotiateContent" property.
	 * If it is disabled and multiple variants are available for the target
	 * resource, then a 300 (Multiple Choices) status will be returned with the
	 * list of variants URI if available.
	 */
	public void handleGet() {
		// The variant that may need to meet the request conditions
		Variant selectedVariant = null;

		List<Variant> variants = getVariants();
		if ((variants == null) || (variants.isEmpty())) {
			// Resource not found
			getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
		} else if (isNegotiateContent()) {
			Variant preferredVariant = getPreferredVariant();

			// Set the variant dimensions used for content negotiation
			getResponse().getDimensions().clear();
			getResponse().getDimensions().add(Dimension.CHARACTER_SET);
			getResponse().getDimensions().add(Dimension.ENCODING);
			getResponse().getDimensions().add(Dimension.LANGUAGE);
			getResponse().getDimensions().add(Dimension.MEDIA_TYPE);

			if (preferredVariant == null) {
				// No variant was found matching the client preferences
				getResponse().setStatus(Status.CLIENT_ERROR_NOT_ACCEPTABLE);

				// The list of all variants is transmitted to the client
				ReferenceList refs = new ReferenceList(variants.size());
				for (Variant variant : variants) {
					if (variant.getIdentifier() != null) {
						refs.add(variant.getIdentifier());
					}
				}

				getResponse().setEntity(refs.getTextRepresentation());
			} else {
				getResponse().setEntity(getRepresentation(preferredVariant));
				selectedVariant = preferredVariant;
			}
			selectedVariant = getResponse().getEntity();
		} else {
			if (variants.size() == 1) {
				getResponse().setEntity(getRepresentation(variants.get(0)));
				selectedVariant = getResponse().getEntity();
			} else {
				ReferenceList variantRefs = new ReferenceList();

				for (Variant variant : variants) {
					if (variant.getIdentifier() != null) {
						variantRefs.add(variant.getIdentifier());
					} else {
						getLogger()
								.warning(
										"A resource with multiple variants should provide and identifier for each variants when content negotiation is turned off");
					}
				}

				if (variantRefs.size() > 0) {
					// Return the list of variants
					getResponse()
							.setStatus(Status.REDIRECTION_MULTIPLE_CHOICES);
					getResponse()
							.setEntity(variantRefs.getTextRepresentation());
				} else {
					getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
				}
			}
		}

		// The given representation (even if null) must meet the request
		// conditions
		// (if any).
		if (getRequest().getConditions().hasSome()) {
			Status status = getRequest().getConditions().getStatus(
					getRequest().getMethod(), selectedVariant);
			if (status != null) {
				getResponse().setStatus(status);
				getResponse().setEntity(null);
			}
		}
	}

	/**
	 * Handles a HEAD call, using a logic similar to the handleGet method.
	 */
	public void handleHead() {
		handleGet();
	}

	/**
	 * Handles an OPTIONS call introspecting the target resource (as provided by
	 * the 'findTarget' method).
	 */
	public void handleOptions() {
		// HTTP spec says that OPTIONS should return the list of allowed methods
		updateAllowedMethods();
		getResponse().setStatus(Status.SUCCESS_OK);
	}

	/**
	 * Handles a POST call invoking the 'post' method of the target resource (as
	 * provided by the 'findTarget' method).
	 */
	public void handlePost() {
		if (getRequest().isEntityAvailable()) {
			post(getRequest().getEntity());
		} else {
			getResponse().setStatus(
					new Status(Status.CLIENT_ERROR_BAD_REQUEST,
							"Missing request entity"));
		}
	}

	/**
	 * Handles a PUT call invoking the 'put' method of the target resource (as
	 * provided by the 'findTarget' method).
	 */
	@SuppressWarnings("unchecked")
	public void handlePut() {
		boolean bContinue = true;

		if (getRequest().getConditions().hasSome()) {
			Variant preferredVariant = null;

			if (isNegotiateContent()) {
				preferredVariant = getPreferredVariant();
			} else {
				List<Variant> variants = getVariants();

				if (variants.size() == 1) {
					preferredVariant = variants.get(0);
				} else {
					getResponse().setStatus(
							Status.CLIENT_ERROR_PRECONDITION_FAILED);
					bContinue = false;
				}
			}
			// The conditions have to be checked even if there is no preferred
			// variant.
			if (bContinue) {
				Status status = getRequest().getConditions().getStatus(
						getRequest().getMethod(), preferredVariant);
				if (status != null) {
					getResponse().setStatus(status);
					bContinue = false;
				}
			}
		}

		if (bContinue) {
			// Check the Content-Range HTTP Header in order to prevent usage of
			// partial PUTs
			Object oHeaders = getRequest().getAttributes().get(
					"org.restlet.http.headers");
			if (oHeaders != null) {
				Series<Parameter> headers = (Series<Parameter>) oHeaders;
				if (headers.getFirst("Content-Range", true) != null) {
					getResponse()
							.setStatus(
									new Status(
											Status.SERVER_ERROR_NOT_IMPLEMENTED,
											"the Content-Range header is not understood"));
					bContinue = false;
				}
			}
		}

		if (bContinue) {
			if (getRequest().isEntityAvailable()) {
				put(getRequest().getEntity());

				// HTTP spec says that PUT may return the list of allowed
				// methods
				updateAllowedMethods();
			} else {
				getResponse().setStatus(
						new Status(Status.CLIENT_ERROR_BAD_REQUEST,
								"Missing request entity"));
			}
		}
	}

	/**
	 * Initialize the resource with its context. If you override this method,
	 * make sure that you don't forget to call super.init() first, otherwise
	 * your Resource won't behave properly.
	 * 
	 * @param context
	 *            The parent context.
	 * @param request
	 *            The request to handle.
	 * @param response
	 *            The response to return.
	 */
	public void init(Context context, Request request, Response response) {
		this.context = context;
		this.logger = (context != null) ? context.getLogger() : null;
		this.negotiateContent = true;
		this.request = request;
		this.response = response;
		this.variants = null;
	}

	/**
	 * Invokes a method with the given arguments.
	 * 
	 * @param method
	 *            The method to invoke.
	 * @param args
	 *            The arguments to pass.
	 * @return Invocation result.
	 */
	private Object invoke(java.lang.reflect.Method method, Object... args) {
		Object result = null;

		if (method != null) {
			try {
				result = method.invoke(this, args);
			} catch (Exception e) {
				getLogger().log(
						Level.WARNING,
						"Couldn't invoke the handle method for \"" + method
								+ "\"", e);
			}
		}

		return result;
	}

	/**
	 * Indicates if the best content is automatically negotiated. Default value
	 * is true.
	 * 
	 * @return True if the best content is automatically negotiated.
	 */
	public boolean isNegotiateContent() {
		return this.negotiateContent;
	}

	/**
	 * Posts a representation to the resource.
	 * 
	 * @param entity
	 *            The posted entity.
	 */
	public void post(Representation entity) {
		getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
	}

	/**
	 * Puts a representation in the resource.
	 * 
	 * @param entity
	 *            A new or updated representation.
	 */
	public void put(Representation entity) {
		getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
	}

	/**
	 * Sets the parent context.
	 * 
	 * @param context
	 *            The parent context.
	 */
	public void setContext(Context context) {
		this.context = context;
	}

	/**
	 * Indicates if the best content is automatically negotiated. Default value
	 * is true.
	 * 
	 * @param negotiateContent
	 *            True if the best content is automatically negotiated.
	 */
	public void setNegotiateContent(boolean negotiateContent) {
		this.negotiateContent = negotiateContent;
	}

	/**
	 * Sets the request to handle.
	 * 
	 * @param request
	 *            The request to handle.
	 */
	public void setRequest(Request request) {
		this.request = request;
	}

	/**
	 * Sets the response to update.
	 * 
	 * @param response
	 *            The response to update.
	 */
	public void setResponse(Response response) {
		this.response = response;
	}

	/**
	 * Updates the set of allowed methods on the response.
	 */
	private void updateAllowedMethods() {
		Set<Method> allowedMethods = getResponse().getAllowedMethods();
		for (java.lang.reflect.Method classMethod : getClass().getMethods()) {
			if (classMethod.getName().startsWith("allow")
					&& (classMethod.getParameterTypes().length == 0)) {
				if ((Boolean) invoke(classMethod)) {
					Method allowedMethod = Method.valueOf(classMethod.getName()
							.substring(5));
					allowedMethods.add(allowedMethod);
				}
			}
		}
	}

}
